<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>导入导出Excel</title>
  <style>
    table {
      width: 50%;
      margin: auto;
      margin-top: 50px;
      text-align: center;
      border-collapse: collapse;
    }

    th {
      background-color: #dedede;
    }

    caption {
      padding: 10px 0;
      font-weight: bold;
    }
  </style>
</head>

<body>
  <h1>
    <a href="https://github.com/rockboom/SheetJS-docs-zh-CN" target="_blank">官方参考文档</a>
    <a href="https://www.cnblogs.com/liuxianan/p/js-excel.html" target="_blank">参考文章</a>
  </h1>
  <div>
    <input type="file" id="files" style="display: none" onchange="fileImport();">
    <input type="button" id="fileImport" value="导入">
    <input type="button" id="fileOutput" value="table_to_sheet">
    <input type="button" id="aoa2sheet" value="aoa2sheet">
    <input type="button" id="tdMarge" value="单元格的合并">
  </div>

  <table border="1">
    <caption>表格导出为Excel</caption>
    <tr>
      <th>姓名</th>
      <th>性别</th>
      <th>电话</th>
    </tr>
    <tr>
      <td style="background-color: aqua; color: red; text-align: left; font-size: 30px;">Jack</td>
      <td>男</td>
      <td>555 77 855</td>
    </tr>
    <tr>
      <td>Rose</td>
      <td>女</td>
      <td>555 77 855</td>
    </tr>
    <tr>
      <td>范特西</td>
      <td>男</td>
      <td>123 23 344</td>
    </tr>
  </table>

  <div id="result"></div>

  <script src="https://cdn.bootcdn.net/ajax/libs/xlsx/0.17.0/xlsx.core.min.js"></script>
  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
  <script>
    //点击导入按钮,使files触发点击事件,然后完成读取文件的操作
    $("#fileImport").click(function () {
      $("#files").click();
    })

    // 导出
    $("#fileOutput").click(function () {
      table2sheet()
    })
    $("#aoa2sheet").click(function () {
      aoa2sheet()
    })
    $("#tdMarge").click(function () {
      tdMerge()
    })

    // 

    /* 
      * 读取excel主要是通过XLSX.read(data, {type: type});方法来实现，
      * 返回一个叫WorkBook的对象，type主要取值如下：
      * base64: 以base64方式读取；
      * binary: BinaryString格式(byte n is data.charCodeAt(n))
      * string: UTF8编码的字符串；
      * buffer: nodejs Buffer；
      * array: Uint8Array，8位无符号数组；
      * file: 文件的路径（仅nodejs下支持）；
    */

    function fileImport() {
      //获取读取我文件的File对象
      var selectedFile = document.getElementById('files').files[0];
      var name = selectedFile.name;//读取选中文件的文件名
      var size = selectedFile.size;//读取选中文件的大小
      console.log("文件名:" + name + "大小:" + size);
      readWorkbookFromLocalFile(selectedFile, (wb) => {
        console.log(wb);
        readWorkbook(wb);
      });
    }

    // 获取workbook对象
    function readWorkbookFromLocalFile(file, callback) {
      var reader = new FileReader();
      reader.onload = function (e) {
        var data = e.target.result;
        var workbook = XLSX.read(data, { type: 'binary' });
        if (callback) callback(workbook);
      };
      reader.readAsBinaryString(file);
    }

    // 从网络上读取某个excel文件，url必须同域，否则报错
    function readWorkbookFromRemoteFile(url, callback) {
      var xhr = new XMLHttpRequest();
      xhr.open('get', url, true);
      xhr.responseType = 'arraybuffer';
      xhr.onload = function (e) {
        if (xhr.status == 200) {
          var data = new Uint8Array(xhr.response)
          var workbook = XLSX.read(data, { type: 'array' });
          if (callback) callback(workbook);
        }
      };
      xhr.send();
    }

    // 读取workbook, 读取 excel文件
    function outputWorkbook(workbook) {
      var sheetNames = workbook.SheetNames; // 工作表名称集合
      sheetNames.forEach(name => {
        var worksheet = workbook.Sheets[name]; // 只能通过工作表名称来获取指定工作表
        for (var key in worksheet) {
          // v是读取单元格的原始值
          console.log(key, key[0] === '!' ? worksheet[key] : worksheet[key].v);
        }
      });
    }

    function readWorkbook(workbook) {
      var sheetNames = workbook.SheetNames; // 工作表名称集合
      var worksheet = workbook.Sheets[sheetNames[0]]; // 这里我们只读取第一张sheet
      var csv = XLSX.utils.sheet_to_csv(worksheet);
      document.getElementById('result').innerHTML = csv2table(csv);
    }

    /* 
      XLSX.utils.sheet_to_csv：生成CSV格式
      XLSX.utils.sheet_to_txt：生成纯文本格式
      XLSX.utils.sheet_to_html：生成HTML格式
      XLSX.utils.sheet_to_json：输出JSON格式
    */

    // 将csv转换成简单的表格，会忽略单元格合并，在第一行和第一列追加类似excel的索引
    function csv2table(csv) {
      var html = '<table border="1">';
      var rows = csv.split('\n');
      rows.pop(); // 最后一行没用的
      rows.forEach(function (row, idx) {
        var columns = row.split(',');
        columns.unshift(idx + 1); // 添加行索引
        if (idx == 0) { // 添加列索引
          html += '<tr>';
          for (var i = 0; i < columns.length; i++) {
            html += '<th>' + (i == 0 ? '' : String.fromCharCode(65 + i - 1)) + '</th>';
          }
          html += '</tr>';
        }
        html += '<tr>';
        columns.forEach(function (column) {
          html += '<td>' + column + '</td>';
        });
        html += '</tr>';
      });
      html += '</table>';
      return html;
    }

    // csv转sheet对象
    function csv2sheet(csv) {
      var sheet = {}; // 将要生成的sheet
      csv = csv.split('\n');
      csv.forEach(function (row, i) {
        row = row.split(',');
        if (i == 0) sheet['!ref'] = 'A1:' + String.fromCharCode(65 + row.length - 1) + (csv.length - 1);
        row.forEach(function (col, j) {
          sheet[String.fromCharCode(65 + j) + (i + 1)] = { v: col };
        });
      });
      return sheet;
    }

    // 将一个sheet转成最终的excel文件的blob对象，然后利用URL.createObjectURL下载
    function sheet2blob(sheet, sheetName) {
      sheetName = sheetName || 'sheet1';
      var workbook = {
        SheetNames: [sheetName],
        Sheets: {}
      };
      workbook.Sheets[sheetName] = sheet;
      // 生成excel的配置项
      var wopts = {
        bookType: 'xlsx', // 要生成的文件类型
        bookSST: false, // 是否生成Shared String Table，官方解释是，如果开启生成速度会下降，但在低版本IOS设备上有更好的兼容性
        type: 'binary'
      };
      var wbout = XLSX.write(workbook, wopts);
      var blob = new Blob([s2ab(wbout)], { type: "application/octet-stream" });
      // 字符串转ArrayBuffer
      function s2ab(s) {
        var buf = new ArrayBuffer(s.length);
        var view = new Uint8Array(buf);
        for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
        return buf;
      }
      return blob;
    }

    /**
     * 通用的打开下载对话框方法，没有测试过具体兼容性
     * @param url 下载地址，也可以是一个blob对象，必选
     * @param saveName 保存文件名，可选
   */
    function openDownloadDialog(url, saveName) {
      if (typeof url == 'object' && url instanceof Blob) {
        url = URL.createObjectURL(url); // 创建blob地址
      }
      var aLink = document.createElement('a');
      aLink.href = url;
      aLink.download = saveName || ''; // HTML5新增的属性，指定保存文件名，可以不要后缀，注意，file:///模式下不会生效
      var event;
      if (window.MouseEvent) event = new MouseEvent('click');
      else {
        event = document.createEvent('MouseEvents');
        event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
      }
      aLink.dispatchEvent(event);
    }

    // 传入csv，执行后就会弹出下载框
    function exportExcel(csv) {
      var sheet = csv2sheet(csv);
      var blob = sheet2blob(sheet);
      openDownloadDialog(blob, '导出.xlsx');
    }

    // --------------------利用官方工具类生成--------------------------
    // aoa_to_sheet: 这个工具类最强大也最实用了，将一个二维数组转成sheet，会自动处理number、string、boolean、date等类型数据；
    // table_to_sheet: 将一个table dom直接转成sheet，会自动识别colspan和rowspan并将其转成对应的单元格合并；
    // json_to_sheet: 将一个由对象组成的数组转成sheet；

    // aoa_to_sheet示例：
    function aoa2sheet() {
      var aoa = [
        ['姓名', '性别', '年龄', '注册时间'],
        ['张三', '男', 18, new Date()],
        ['李四', '女', 22, new Date()]
      ];
      var sheet = XLSX.utils.aoa_to_sheet(aoa);
      openDownloadDialog(sheet2blob(sheet), 'aoa2sheet.xlsx');
    }

    // table_to_sheet就更简单了，直接XLSX.utils.table_to_sheet($('table')[0])即可；
    function table2sheet() {
      var sheet = XLSX.utils.table_to_sheet($('table')[0]);
      openDownloadDialog(sheet2blob(sheet), 'table2sheet.xlsx');
    }

    // 处理单元格合并
    function tdMerge() {
      var aoa = [
        ['主要信息', null, null, '其它信息'], // 特别注意合并的地方后面预留2个null
        ['姓名', '性别', '年龄', '注册时间'],
        ['张三', '男', 18, new Date()],
        ['李四', '女', 22, new Date()]
      ];
      var sheet = XLSX.utils.aoa_to_sheet(aoa);
      sheet['!merges'] = [
        // 设置A1-C1的单元格合并
        { s: { r: 0, c: 0 }, e: { r: 0, c: 2 } }
      ];
      openDownloadDialog(sheet2blob(sheet), '单元格合并示例.xlsx');
    }
  </script>
</body>

</html>